package com.lorizz.index.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.Version;

import com.lorizz.index.Action;
import com.lorizz.index.Indexer;
import com.lorizz.index.Item;
import com.lorizz.index.Request;
import com.lorizz.index.Result;
import com.lorizz.index.SortItem;

public abstract class AbstractIndexer<T> implements Indexer<T> {

	private Log log = LogFactory.getLog(getClass());
	private IndexWriter indexWriter;
	private QueryParser parser;
	private static final int DEFAULT_TOP_HITS = 100; 

	@Override
	public synchronized void index(List<Item<T>> items) {
		IndexWriter idxWriter = getIndexWriter();
		try {
			for (Item<T> item : items) {
				Term idTerm = getIdTerm(item);
				if (item.getAction() != Action.ADD) {
					log.debug(String.format("Deleting item where [%s] is [%s] ...", idTerm.field(), idTerm.text()));
					idxWriter.deleteDocuments(idTerm);
				}
				if (item.getAction() != Action.DELETE) {
					Document document = composeDocument(item);
					Field idField = new Field(idTerm.field(), idTerm.text(), Field.Store.YES, Field.Index.NOT_ANALYZED);
					document.add(idField);
					log.debug(String.format("Indexing item where [%s] is [%s] ...", idTerm.field(), idTerm.text()));
					idxWriter.addDocument(document);
				}
			}
			idxWriter.commit();
		} catch (CorruptIndexException e) {
			log.error(e);
			throw new RuntimeException(e);
		} catch (IOException e) {
			log.error(e);
			throw new RuntimeException(e);
		}

	}

	private IndexWriter getIndexWriter() {
		if (indexWriter == null) {
			IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_33, getAnalyzer());
			iwc.setOpenMode(OpenMode.CREATE_OR_APPEND);
			try {
				indexWriter = new IndexWriter(getDirectory(), iwc);
			} catch (Exception e) {
				throw new RuntimeException(e);
			} 
		}
		return indexWriter;
	}

	protected abstract Document composeDocument(Item<T> item);

	protected abstract Term getIdTerm(Item<T> item);

	protected abstract Analyzer getAnalyzer();
	
	protected abstract String getDefaultField();

	protected abstract Directory getDirectory();
	
	protected abstract List<T> composeItems(List<Document> docs);
	
	@Override
	public Result<T> search(Request request) {
		try {
			IndexSearcher searcher = new IndexSearcher(getDirectory());
			if (parser == null) {
				parser = new QueryParser(Version.LUCENE_33, getDefaultField(), getAnalyzer());
			}
			int max = DEFAULT_TOP_HITS;
			if (request.getPageSize() > 0 && request.getStart() >=1)
				max = request.getStart() + request.getPageSize() - 1;
			TopDocs topDocs = null;
			if (request.getSortItems() != null && !request.getSortItems().isEmpty()) {
				List<SortField> fields = new ArrayList<SortField>();
				for (SortItem si : request.getSortItems()) {
					SortField sortField = new SortField(si.getField(), SortField.STRING, !si.isAscending());
					fields.add(sortField);
				}
				Sort sort = new Sort(fields.toArray(new SortField[fields.size()]));
				topDocs = searcher.search(parser.parse(request.getQueryString()), max, sort);
			} else {
				topDocs = searcher.search(parser.parse(request.getQueryString()), max);
			}
			ResultImpl<T> result = new ResultImpl<T>();
			result.setTotal(topDocs.totalHits);
			result.setSortItems(request.getSortItems());
			ScoreDoc[] hits = topDocs.scoreDocs;
			if (request.getPageSize() > 0 && request.getStart() >= 1) {
				List<ScoreDoc> filtered = new ArrayList<ScoreDoc>();
				int i = request.getStart() - 1;
				while (i < hits.length) {
					filtered.add(hits[i]);
					i++;
				}
				hits = filtered.toArray(new ScoreDoc[filtered.size()]);
				result.setLast(max);
			}
			List<Document> docs = new ArrayList<Document>();
			for (ScoreDoc hit : hits) {
				docs.add(searcher.doc(hit.doc));
			}
			result.getItems().addAll(composeItems(docs));
			return result;
		} catch (CorruptIndexException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		} catch (ParseException e) {
			throw new RuntimeException(e);
		}
	}

	

	public void destroy() {
		if (indexWriter != null) {
			log.debug("Closing index writer ...");
			try {
				indexWriter.close();
			} catch (Exception e) {
				log.error("Destroying indexer writer error.", e);
				e.printStackTrace();
			} finally {
				try {
					if (IndexWriter.isLocked(getDirectory())) {
						IndexWriter.unlock(getDirectory());
					}
				} catch (IOException e) {
					log.error("Destroying indexer writer error.", e);
					e.printStackTrace();
				}
			}
		}
	}
}
